﻿using Bettzueche.RLLibrary.Types;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Bettzueche.DampfrossContinuum
{
    /// <summary>
    /// Trennung / Unabhängigkeit zwischen Geographie und Navigatorik:
    /// Nachbarn konjunktiv verknüpft, außer(!) Zentrum; Distanz und Richtung konjuntiv verknüpft;
    /// Beides zusammen disjunktiv verknüpft.
    /// Gesamtzahl Merkmale: (|uTiles|^6 + |ztrmTiles|) + (|Richtungen| * |Entfernungen|).
    /// Bei 9 u-Tiles (LandschaftsTiles): 9^6 + 2 + 6*10 = 531.503
    /// Anzahl Merkmale eines Zustands: 1 bis |uTilings| + 1 (Ztrm) + 1 (Richt/Entf) = 3 bis |uTilings|+2
    /// </summary>
    class FeatureCombi2 : IFeatures<UmgebungC>
    {
        #region Properties

        /// <summary>
        /// Gets the kachel wert unterteilung.
        /// </summary>
        /// <value>
        /// The kachel wert unterteilung.
        /// </value>
        public LandschaftsTiling KachelWertUnterteilung {
            get;
            private set;
        }

        /// <summary>
        /// Gets the zentrums feld wert unterteilung.
        /// </summary>
        /// <value>
        /// The zentrums feld wert unterteilung.
        /// </value>
        public ZentrumTiling ZentrumsFeldWertUnterteilung { get; private set; }

        /// <summary>
        /// Gets the entfernungs unterteilung.
        /// </summary>
        /// <value>
        /// The entfernungs unterteilung.
        /// </value>
        public EntfernungTiling EntfernungsUnterteilung { get; private set; }

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="FeatureCombi1"/> class.
        /// Nur das LandschaftsTiling ist variabel und kann übergeben werden.
        /// Für Zentrum, Distanz und Richtung gibt es nur Standard-Tilings.
        /// </summary>
        /// <param name="kachelTiling">The kachel tiling.</param>
        /// <exception cref="System.ArgumentNullException">kachelTiling</exception>
        public FeatureCombi2(LandschaftsTiling kachelTiling) {
            if (kachelTiling == null)
                throw new ArgumentNullException("kachelTiling");
            ZentrumsFeldWertUnterteilung = new ZentrumTiling();
            EntfernungsUnterteilung = new EntfernungTiling();
            KachelWertUnterteilung = kachelTiling;
        }


        #region IFeatures<UmgebungC>

        /// <summary>
        /// Errechnet das Feature Set (Indizes der Merkmale dieses Zustands).
        /// Gibt die Merkmalsmenge(bzw. die Merkmalsnummern), die aus den Zustandseigenschaften bspw. mittels 
        /// <c>MultiTiling</c> ermittelt werden.
        /// </summary>
        /// <param name="state">The state.</param>
        /// <returns>Merkmalindizes. IDs (Indizes) der Merkmale, die zu den übergebenen Zustandsdaten gehören.</returns>
        /// <exception cref="System.NullReferenceException">if state or its properties are null</exception>.
        public int[] GetFeatureSet(UmgebungC state) {
            int oobIndizes = 0; // indizes des Zwischenergebnis-Vektors mit OutOfBounds
            int anzNachbarn = state.Nachbarn.Length; // = config.Abschnitte;
            double[] unebenheiten = state.GetMeasures().ToArray();

            // Je Tiling eines MultiTiling gibt es ein Merkmal + 1 für Zentrum + 1 für Richtung&Distanz
            // Note: Bei MultiTiling kann es mehrere Merkmale je Zustand geben. Hier nur MultiTiling für LandschaftsTiling.
            int[] featureNums = new int[KachelWertUnterteilung.TilingCount + 2];
            // Beginne mit Unebenheitsmerkmal des Zentrumfeldes:
            int ztrmNum   = ZentrumTiling.GetFeatureNumber(unebenheiten[unebenheiten.Length-1]);
            // Das ZentrumTiling gibt nur ein (unabhängiges) Merkmal - es ist ein Single-Tiling. Der Wert
            // wird nicht weiter verknüpft und hier als letztes Merkmal hinzugefügt. 
            featureNums[featureNums.Length-1] = ztrmNum;
            if (ztrmNum == Tiling.OutOfBounds) // Sonderbehandlung für OutOfBounds?
                oobIndizes++;

            // Distanz und Richtung als (vorletztes) Merkmal verknüpfen
            int indexOffset = ZentrumTiling.TileCount; // = 2 für das ZentrumTiling
            int dirOffset = 6; // = Anzahl Richtungen: Enum.GetValues(typeof(HexRichtungen)).Length;
            int entfNr = EntfernungTiling.GetFeatureNumber(state.Entfernung);
            if (entfNr != Tiling.OutOfBounds) {
                entfNr *= dirOffset;
                int richtungNr = (int)state.RichtungZiel;
                entfNr += richtungNr;
                featureNums[featureNums.Length-2] = entfNr + indexOffset;
            }
            else {
                oobIndizes++;
                featureNums[featureNums.Length-2] = Tiling.OutOfBounds;
            }

            indexOffset = ZentrumTiling.TileCount + EntfernungTiling.TileCount * dirOffset/*Richtungen*/; // 2 + 6*10 = 62
            int nachbarOffset = 1; // erster Nachbar ohne Offset-Faktor
            // zu jedem Nachbar-Abschnitt Unebenheits-Merkmale ermitteln
            for (int abschnittsNr = 0; abschnittsNr < anzNachbarn; abschnittsNr++) {
                // Merkmale gemäß Tilings-Organisation:
                double u_ab = unebenheiten[abschnittsNr];
                int[] merkmale_u_ab = KachelWertUnterteilung.GetFeatureNumbers(u_ab);
                // MerkmalIndizes anpassen gemäß Merkmale je Abschnitt:
                for (int i = 0; i < merkmale_u_ab.Length; i++) {
                    if (featureNums[i] == Tiling.OutOfBounds)
                        continue; // Merkmal ist bereits außerhalb: weiter
                    if (merkmale_u_ab[i] == Tiling.OutOfBounds) {
                        // Diese Dimension ist außerhalb: Merkmal ist außerhalb
                        featureNums[i] = Tiling.OutOfBounds;
                        oobIndizes++;
                    }
                    else {
                        merkmale_u_ab[i] *= nachbarOffset;
                        featureNums[i] += merkmale_u_ab[i];
                    }
                }
                nachbarOffset *= KachelWertUnterteilung.TileCount; // Offset für nächste Kachel mit Anz-U-Werte multiplizieren.
            }
            // Index-Offset von Ztrm, Dir&Dist hinzuaddieren
            for (int i = 0; i < KachelWertUnterteilung.TilingCount; i++) {
                if (featureNums[i] != Tiling.OutOfBounds)
                    featureNums[i] += indexOffset;
            }

            // falls ein IndexOutOfBounds vorhanden, Ergebnisvektor reduzieren:
            if (oobIndizes > 0) {
                List<int> resultVec = new List<int>(featureNums.Length);
                for (int i = 0; i < featureNums.Length; i++) {
                    if (featureNums[i] != Tiling.OutOfBounds) {
                        resultVec.Add(featureNums[i]);
                    }
                }
                return resultVec.ToArray();
            }
            else
                return featureNums;
        }

        /// <summary>
        /// Erwartet Array mit Merkmalindizes und, als letztes (!) Element der FeaturedID.
        /// Übernimmt (kopiert) nur diese Werte und setzt sonst keine (!) Eigenschaften, wie z.B. <c>Nachbarn</c>
        /// oder <c>Entfernung</c>!
        /// </summary>
        /// <param name="features">Array mit Merkmal-IDs plus FeatureID (als letztes Element).</param>
        /// <returns></returns>
        /// <remarks>Diese Methode dient nur den Analysen mit <c>Experiment</c>
        /// Theoretisch sollte das Setzen der FeaturedID und MERKMAL-Indizes für meine Zwecke reichen !(?)!
        /// Zur Berechnung des MSE muss ich dem Agenten die "DebugStates" und Sollwerte übergeben.
        /// Diese DebugStates werden als Parameter der Q-Funktion (indexer) übergeben, um den Wert Q[state,action]
        /// mit dem Sollwert zu vergleichen. Der Q[state,action] Indexer ruft state.GetFeatures auf, der wiederum
        /// die in state gespeicherten MERKMAL-Indizes wiedergibt (um die interne Datenbasis zu referenzieren). 
        /// TADA! Alles gut - es werden für das Experiment keine weiteren Properties benötigt.
        /// </remarks>
        public UmgebungC GetStateFromFeatures(int[] features) {
            UmgebungC retVal = new UmgebungC(features);
            return retVal;            
        }

        #endregion

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String" /> that represents this instance.
        /// </returns>
        public override string ToString() {
            return "(|uTiles|^6 + |ztrmTiles|) + (|Richtungen| * |Entfernungen|)";
        }
    }
}
